<template>
  <div class="json-schema-editor">
    <el-row class="row" :gutter="10">
      <el-col :span="8" class="ant-col-name">
        <div :style="{ marginLeft: `${20 * deep}px` }" class="ant-col-name-c">
          <el-button
            v-if="pickValue.type === 'object' || pickValue.type === 'void'"
            type="text"
            style="color: rgba(0, 0, 0, 0.65); margin-right: 8px"
            @click="hidden = !hidden"
          >
            <i :class="hidden ? 'el-icon-arrow-right' : 'el-icon-arrow-down'"></i>
          </el-button>
          <span v-else style="width: 32px; display: inline-block"></span>
          <el-input :disabled="disabled || root" v-model="pickKey" class="ant-col-name-input" @blur="onInputName" />
        </div>
        <el-tooltip v-if="root" :content="local['checked_all']">
          <el-checkbox
            v-model="rootChecked"
            :disabled="!isObject && !isArray"
            class="ant-col-name-required"
            @change="onRootCheck"
          />
        </el-tooltip>
        <el-tooltip v-else :content="local['required']">
          <el-checkbox :disabled="isItem" v-model="checked" class="ant-col-name-required" @change="onCheck" />
        </el-tooltip>
      </el-col>
      <el-col :span="4">
        <el-select v-model="pickValue.type" :disabled="disabledType" class="ant-col-type" @change="onChangeType">
          <el-option v-for="t in TYPE_NAME" :key="t" :label="t" :value="t" />
        </el-select>
      </el-col>
      <el-col :span="6">
        <el-input v-model="pickValue.title" class="ant-col-title" :placeholder="local['title']" @blur="onInputTitle" />
      </el-col>
      <el-col :span="6" class="ant-col-setting">
        <el-tooltip :content="local['adv_setting']">
          <el-button type="text" class="setting-icon" @click="onSetting">
            <i class="el-icon-setting"></i>
          </el-button>
        </el-tooltip>
        <el-tooltip v-if="isObject" :content="local['add_child_node']">
          <el-button type="text" class="plus-icon" @click="addChild">
            <i class="el-icon-plus"></i>
          </el-button>
        </el-tooltip>
        <el-tooltip v-if="!root && !isItem" :content="local['remove_node']">
          <el-button type="text" class="close-icon" @click="removeNode">
            <i class="el-icon-close"></i>
          </el-button>
        </el-tooltip>
      </el-col>
    </el-row>
    <template v-if="!hidden && pickValue.properties && !isArray">
      <json-schema-editor
        v-for="(item, key, index) in pickValue.properties"
        :value="{ [key]: item }"
        :parent="pickValue"
        :key="index"
        :deep="deep + 1"
        :root="false"
        class="children"
        :lang="lang"
        :custom="custom"
      />
    </template>
    <template v-if="isArray">
      <json-schema-editor
        :value="{ items: pickValue.items }"
        :deep="deep + 1"
        disabled
        isItem
        :root="false"
        class="children"
        :lang="lang"
        :custom="custom"
      />
    </template>
    <el-dialog
      :visible.sync="modalVisible"
      v-if="modalVisible"
      :title="local['adv_setting']"
      :close-on-click-modal="false"
      :append-to-body="true"
      :width="'800'"
      @close="handleOk"
      custom-class="json-schema-editor-advanced-modal"
    >
      <h3>基础设置</h3>
      <el-form :model="advancedValue" class="ant-advanced-search-form">
        <el-row :gutter="6">
          <el-col :span="8" v-for="(item, key) in advancedValue" :key="key">
            <el-form-item>
              <span>{{ advancedAttr[key].extra ? (lang == "en_US" ? key : advancedAttr[key].name) : local[key] }}</span>
              <el-input-number
                v-model="advancedValue[key]"
                v-if="advancedAttr[key].type === 'integer' || advancedAttr[key].type === 'number'"
                style="width: 100%"
                :placeholder="key"
              />
              <span v-else-if="advancedAttr[key].type === 'boolean'" style="display: inline-block; width: 100%">
                <el-switch v-model="advancedValue[key]" />
              </span>
              <el-input
                type="textarea"
                @blur="changeEnumValue"
                v-model="enumText"
                :rows="2"
                v-else-if="key === 'enum'"
                :placeholder="local['enum_msg']"
              ></el-input>
              <el-select
                v-else-if="advancedAttr[key].type === 'array'"
                v-model="advancedValue[key]"
                style="width: 100%"
                :placeholder="local[key]"
              >
                <el-option value="">{{ local["nothing"] }}</el-option>
                <el-option v-for="t in advancedAttr[key].enums" :key="t" :label="t" :value="t" />
              </el-select>
              <el-input v-model="advancedValue[key]" v-else style="width: 100%" :placeholder="key" />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>

      <h3 v-show="custom">添加自定义属性</h3>
      <el-form class="ant-advanced-search-form" v-show="custom">
        <el-row :gutter="6">
          <el-col :span="8" v-for="item in customProps" :key="item.key">
            <el-form-item :label="item.key">
              <el-input v-model="item.value" style="width: calc(100% - 30px)" />
              <el-button type="text" @click="removeCustomNode(item.key)" style="width: 30px">
                <i class="el-icon-close"></i>
              </el-button>
            </el-form-item>
          </el-col>
          <el-col :span="8" v-show="addProp.key != undefined">
            <el-form-item :label-width="'0'">
              <el-input v-model="addProp.key" placeholder="属性名" style="width: 100px; margin-right: 10px" />
              <el-input v-model="addProp.value" placeholder="属性值" style="width: calc(100% - 110px)" />
            </el-form-item>
            <!-- <el-form-item>
              <el-input
                slot="label"
                v-model="addProp.key"
                style="width: 100px"
              />
              <el-input v-model="addProp.value" style="width: 100%" />
            </el-form-item> -->
          </el-col>
          <el-col :span="8">
            <el-form-item>
              <el-button type="text" @click="confirmAddCustomNode(null)" v-if="customing">
                <i class="el-icon-check"></i>
              </el-button>
              <el-tooltip :content="local['add_custom']" v-else>
                <el-button type="text" @click="addCustomNode">
                  <i class="el-icon-plus"></i>
                </el-button>
              </el-tooltip>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>

      <h3>预览</h3>
      <pre style="width: 100%">{{ completeNodeValue }}</pre>
    </el-dialog>
  </div>
</template>
<script>
import { isNull, renamePropertyAndKeepKeyPrecedence, arrayDifference } from "./util";
import { TYPE_NAME, TYPE } from "./type/type";

import LocalProvider from "./LocalProvider";

export default {
  name: "JsonSchemaEditor",
  components: {},
  props: {
    value: {
      type: Object,
      required: true,
    },
    disabled: {
      //name不可编辑，根节点name不可编辑,数组元素name不可编辑
      type: Boolean,
      default: false,
    },
    disabledType: {
      //禁用类型选择
      type: Boolean,
      default: false,
    },
    isItem: {
      //是否数组元素
      type: Boolean,
      default: false,
    },
    deep: {
      // 节点深度，根节点deep=0
      type: Number,
      default: 0,
    },
    root: {
      //是否root节点
      type: Boolean,
      default: true,
    },
    parent: {
      //父节点
      type: Object,
      default: null,
    },
    custom: {
      //enable custom properties
      type: Boolean,
      default: false,
    },
    lang: {
      // i18n language
      type: String,
      default: "zh_CN",
    },
    extra: {
      // 基础设置里追加的设置
      type: Object,
      default: null,
    },
  },
  watch: {},
  computed: {
    pickValue() {
      return Object.values(this.value)[0];
    },
    // pickKey() {
    //   return Object.keys(this.value)[0];
    // },
    pickKey: {
      get() {
        return Object.keys(this.value)[0];
      },
      set(newKey) {
        this.onInputName({ target: { value: newKey } });
      },
    },
    isObject() {
      return this.pickValue.type === "object" || this.pickValue.type === 'void';
    },
    isArray() {
      return this.pickValue.type === "array";
    },
    checked() {
      return this.parent && this.parent.required && this.parent.required.indexOf(this.pickKey) >= 0;
    },
    // checked: {
    //   get() {
    //     return this.parent && this.parent.required && this.parent.required.indexOf(this.pickKey) >= 0;
    //   },
    //   set(newKey) {
    //     console.log(newKey, this.parent);

    //     this._checked(newKey, this.parent);
    //   },
    // },
    advanced() {
      return TYPE[this.pickValue.type];
    },
    advancedAttr() {
      return TYPE[this.pickValue.type].attr;
    },
    ownProps() {
      return ["type", "title", "properties", "items", "required", ...Object.keys(this.advancedAttr)];
    },
    advancedNotEmptyValue() {
      const jsonNode = Object.assign({}, this.advancedValue);
      for (let key in jsonNode) {
        isNull(jsonNode[key]) && delete jsonNode[key];
      }
      return jsonNode;
    },
    completeNodeValue() {
      const t = {};
      const basicValue = { ...this.pickValue };
      for (const item of this.customProps) {
        t[item.key] = item.value;
      }
      this._pickDiffKey().forEach((key) => delete basicValue[key]);
      return Object.assign({}, basicValue, t, this.advancedNotEmptyValue);
    },
    enumText() {
      const t = this.advancedValue["enum"];
      if (!t) return "";
      if (!t.length) return "";
      return t.join("\n");
    },
  },
  data() {
    return {
      TYPE_NAME,
      hidden: false,
      countAdd: 1,
      modalVisible: false,
      advancedValue: {},
      addProp: {}, // 自定义属性
      customProps: [],
      customing: false,
      local: LocalProvider(this.lang),
      rootChecked: false,
    };
  },
  mounted() {
    this.init();
  },
  methods: {
    init() {
      if (!this.extra || JSON.stringify(this.extra) === "{}") return;

      const keys = Object.keys(this.extra);
      const nonCompliant = arrayDifference(TYPE_NAME, keys);
      if (nonCompliant.length > 0) {
        console.warn(
          "not compliant extra，Supports only the following keys：string, number, integer,object, array,  boolean",
          nonCompliant
        );
      }
      for (let item in this.extra) {
        const setting = this.extra[item];
        const basicSetting = TYPE[item];
        Object.entries(setting).forEach(([key, value]) => {
          value.extra = true;
          basicSetting.attr[key] = value;
          basicSetting.value[key] = null;
        });
      }
    },
    onInputName(e) {
      const oldKey = this.pickKey;
      const newKey = e.target.value;
      if (oldKey === newKey) return;

      // const nodeValue = this.parent.properties[oldKey];

      // // 替换key名
      // this.$delete(this.parent.properties, oldKey);
      // this.$set(this.parent.properties, newKey, nodeValue);
      const _this = this;
      renamePropertyAndKeepKeyPrecedence(_this, this.parent.properties, [oldKey, newKey]);

      // required重新设置
      const requireds = this.parent.required || [];
      const oldIndex = requireds.indexOf(oldKey);
      if (requireds.length > 0 && oldIndex > -1) {
        requireds.splice(oldIndex, 1);
        requireds.push(newKey);
        this.$set(this.parent, "required", [...new Set(requireds)]);
      }
    },
    onInputTitle(e) {
      this.$set(this.pickValue, "title", e.target.value);
    },
    onChangeType() {
      this.parseCustomProps();
      // 删除自定义属性
      this.customProps.forEach((item) => {
        this.$delete(this.pickValue, item.key);
      });
      this.customProps = [];

      this.$delete(this.pickValue, "properties");
      this.$delete(this.pickValue, "items");
      this.$delete(this.pickValue, "required");
      this.$delete(this.pickValue, "format");
      this.$delete(this.pickValue, "enum");

      if (this.isArray) {
        this.$set(this.pickValue, "items", { type: "string" });
      }
    },
    onCheck(e) {
      this._checked(e, this.parent);
    },
    onRootCheck(e) {
      this._deepCheck(e, this.pickValue);
    },
    changeEnumValue(e) {
      const pickType = this.pickValue.type;
      const value = e.target.value;

      if (!value || value === "") {
        this.advancedValue.enum = null;
        return;
      }
      var arr = value.split("\n");

      if (pickType === "string") {
        this.advancedValue.enum = arr.map((item) => item);
      } else {
        if (arr.length === 0 || (arr.length === 1 && arr[0] == "")) {
          this.advancedValue.enum = null;
        } else {
          this.advancedValue.enum = arr.map((item) => +item);
        }
      }
    },
    _deepCheck(checked, node) {
      if (node.type === "object" && node.properties) {
        checked ? this.$set(node, "required", Object.keys(node.properties)) : this.$delete(node, "required");
        Object.keys(node.properties).forEach((key) => this._deepCheck(checked, node.properties[key]));
      } else if (node.type === "array" && node.items.type === "object") {
        checked
          ? this.$set(node.items, "required", Object.keys(node.items.properties))
          : this.$delete(node.items, "required");
        Object.keys(node.items.properties).forEach((key) => this._deepCheck(checked, node.items.properties[key]));
      }
    },
    _checked(checked, parent) {
      let required = parent.required;
      if (checked) {
        required || this.$set(this.parent, "required", []);

        required = this.parent.required;
        required.indexOf(this.pickKey) === -1 && required.push(this.pickKey);
      } else {
        const pos = required.indexOf(this.pickKey);
        pos >= 0 && required.splice(pos, 1);
      }
      required.length === 0 && this.$delete(parent, "required");
    },
    addChild() {
      const name = this._joinName();
      const type = "string";
      const node = this.pickValue;
      node.properties || this.$set(node, "properties", {});
      const props = node.properties;
      this.$set(props, name, { type: type, title: "" });
    },
    parseCustomProps() {
      const ownProps = this.ownProps;
      Object.keys(this.pickValue).forEach((key) => {
        if (ownProps.indexOf(key) === -1) {
          this.confirmAddCustomNode({ key: key, value: this.pickValue[key] });
          // this.$delete(this.pickValue,key)
        }
      });
    },
    addCustomNode() {
      this.$set(this.addProp, "key", this._joinName());
      this.$set(this.addProp, "value", "");
      this.customing = true;
    },
    removeCustomNode(key) {
      this.customProps.forEach((item, index) => {
        if (item.key === key) {
          this.customProps.splice(index, 1);
          return;
        }
      });
    },
    confirmAddCustomNode(prop) {
      const p = prop || this.addProp;
      let existKey = false;
      this.customProps.forEach((item) => {
        if (item.key === p.key) {
          existKey = true;
        }
      });
      if (existKey) {
        this.$message.error("属性名重复");
        return;
      }

      if (p.value.includes && p.value.includes("object-")) {
        p.value = p.value.replace("object-", "");
        p.value = JSON.parse(p.value);
      }

      this.customProps.push(p);
      this.addProp = {};
      this.customing = false;
    },
    removeNode() {
      const { properties, required } = this.parent;
      this.$delete(properties, this.pickKey);
      if (required) {
        const pos = required.indexOf(this.pickKey);
        pos >= 0 && required.splice(pos, 1);
        required.length === 0 && this.$delete(this.parent, "required");
      }
    },
    _joinName() {
      return `field_${this.deep}_${this.countAdd++}`;
    },
    onSetting() {
      this.modalVisible = true;
      this.advancedValue = { ...this.advanced.value };
      for (const k in this.advancedValue) {
        if (this.pickValue[k]) {
          this.advancedValue[k] = this.pickValue[k];
        }
      }
      this.parseCustomProps();
    },

    handleOk() {
      this.modalVisible = false;
      for (const key in this.advancedValue) {
        if (isNull(this.advancedValue[key])) {
          this.$delete(this.pickValue, key);
        } else {
          this.$set(this.pickValue, key, this.advancedValue[key]);
        }
      }
      const diffKey = this._pickDiffKey();
      diffKey.forEach((key) => this.$delete(this.pickValue, key));
      for (const item of this.customProps) {
        this.$set(this.pickValue, item.key, item.value);
      }
    },
    _pickDiffKey() {
      const keys = Object.keys(this.pickValue);
      return keys.filter((item) => this.ownProps.indexOf(item) === -1);
    },
  },
};
</script>
<style scoped>
.json-schema-editor .row {
  display: flex;
  margin: 12px;
}
.json-schema-editor .row .ant-col-name {
  display: flex;
  align-items: center;
}
.json-schema-editor .row .ant-col-name .ant-col-name-c {
  display: flex;
  align-items: center;
}
.json-schema-editor .row .ant-col-name .ant-col-name-required {
  flex: 0 0 24px;
  text-align: center;
}
.json-schema-editor .row .ant-col-type {
  width: 100%;
}
.json-schema-editor .row .ant-col-setting {
  display: inline-block;
}
.json-schema-editor .row .setting-icon {
  color: rgba(0, 0, 0, 0.45);
  border: none;
}
.json-schema-editor .row .plus-icon {
  border: none;
}
.json-schema-editor .row .close-icon {
  color: #888;
  border: none;
}
</style>
<style>
.json-schema-editor-advanced-modal {
  color: rgba(0, 0, 0, 0.65);
  min-width: 600px;
}
.json-schema-editor-advanced-modal pre {
  font-family: monospace;
  height: 100%;
  overflow-y: auto;
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-radius: 4px;
  padding: 12px;
  width: 50%;
}
.json-schema-editor-advanced-modal h3 {
  display: block;
  border-left: 3px solid #1890ff;
  padding: 0 8px;
}
.json-schema-editor-advanced-modal .ant-advanced-search-form .ant-form-item {
  display: flex;
}
.json-schema-editor-advanced-modal .ant-advanced-search-form .ant-form-item .ant-form-item-control-wrapper {
  flex: 1;
}
</style>
